unit AVClasses;

interface
uses SysUtils, System.Generics.Defaults, System.Generics.Collections,
     System.RTLConsts;

type

  TAVCustomList<T> = class(TEnumerable<T>)
  private
    FItems: TArray<T>;
    FCount: Integer;
    FComparer: IComparer<T>;
    FOnNotify: TCollectionNotifyEvent<T>;
    FArrayManager: TArrayManager<T>;
    function GetCapacity: Integer;
    procedure SetCapacity(Value: Integer);
    procedure SetCount(Value: Integer);
    procedure Grow(ACount: Integer);
    procedure GrowCheck(ACount: Integer); inline;
  protected
    function Add(const Value: T): Integer;
    procedure Insert(Index: Integer; const Value: T);
    procedure DeleteRange(AIndex, ACount: Integer);
    procedure Notify(const Item: T; Action: TCollectionNotification); virtual;

    property Capacity: Integer read GetCapacity write SetCapacity;
    property Count: Integer read FCount write SetCount;
  public
    constructor Create; overload;
    constructor Create(const AComparer: IComparer<T>); overload;
    destructor Destroy; override;
  end;


implementation

//TAVList<T>

  constructor TAVCustomList<T>.Create;
  begin
    Create(TComparer<T>.Default);
  end;

  constructor TAVCustomList<T>.Create(const AComparer: IComparer<T>);
  begin
    inherited Create;
    FArrayManager := TMoveArrayManager<T>.Create;
    FComparer := AComparer;
    if FComparer = nil then FComparer := TComparer<T>.Default;
  end;

  destructor TAVCustomList<T>.Destroy;
  begin
    Capacity := 0;
    FArrayManager.Free;
    inherited;
  end;

  function TAVCustomList<T>.GetCapacity: Integer;
  begin
    Result := Length(FItems);
  end;

  procedure TAVCustomList<T>.SetCapacity(Value: Integer);
  begin
    if Value < Count then Count := Value;
    SetLength(FItems, Value);
  end;

  procedure TAVCustomList<T>.SetCount(Value: Integer);
  begin
    if Value < 0 then
      raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);
    if Value > Capacity then SetCapacity(Value);
    if Value < Count then DeleteRange(Value, Count - Value);
    FCount := Value;
  end;

  procedure TAVCustomList<T>.Grow(ACount: Integer);
  var
    newCount: Integer;
  begin
    newCount := Length(FItems);
    if newCount = 0 then
      newCount := ACount
    else
      repeat
        newCount := newCount * 2;
        if newCount < 0 then OutOfMemoryError;
      until newCount >= ACount;
    Capacity := newCount;
  end;

  procedure TAVCustomList<T>.GrowCheck(ACount: Integer);
  begin
    if ACount > Length(FItems) then
      Grow(ACount)
    else if ACount < 0 then
      OutOfMemoryError;
  end;

  function TAVCustomList<T>.Add(const Value: T): Integer;
  begin
    GrowCheck(Count + 1);
    Result := Count;
    FItems[Count] := Value;
    Inc(FCount);
    Notify(Value, cnAdded);
  end;

  procedure TAVCustomList<T>.Insert(Index: Integer; const Value: T);
  begin
    if (Index < 0) or (Index > Count) then
      raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);

    GrowCheck(Count + 1);
    if Index <> Count then
    begin
      FArrayManager.Move(FItems, Index, Index + 1, Count - Index);
      FArrayManager.Finalize(FItems, Index, 1);
    end;
    FItems[Index] := Value;
    Inc(FCount);
    Notify(Value, cnAdded);
  end;

  procedure TAVCustomList<T>.Notify(const Item: T; Action: TCollectionNotification);
  begin
    if Assigned(FOnNotify) then FOnNotify(Self, Item, Action);
  end;

  procedure TAVCustomList<T>.DeleteRange(AIndex, ACount: Integer);
  var
    oldItems: array of T;
    tailCount, I: Integer;
  begin
    if (AIndex < 0) or (ACount < 0) or (AIndex + ACount > Count)
      or (AIndex + ACount < 0) then
      raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);
    if ACount = 0 then
      Exit;

    SetLength(oldItems, ACount);
    FArrayManager.Move(FItems, oldItems, AIndex, 0, ACount);

    tailCount := Count - (AIndex + ACount);
    if tailCount > 0 then
    begin
      FArrayManager.Move(FItems, AIndex + ACount, AIndex, tailCount);
      FArrayManager.Finalize(FItems, Count - ACount, ACount);
    end else
      FArrayManager.Finalize(FItems, AIndex, ACount);

    Dec(FCount, ACount);

    for I := 0 to Length(oldItems) - 1 do
      Notify(oldItems[I], cnRemoved);
  end;

end.
